#include "SimpleLogger.h"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <time.h>

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif

#include "Windows.h"


using namespace std;

SimpleLogger& SimpleLogger::Instance()
{
	static SimpleLogger logger;
	return logger;
}

void SimpleLogger::Open( LogLevel level /*= LL_DEBUG*/, int logType /*= LT_ALL*/, std::string logFile /*= "log"*/ )
{
	this->logLevel = logLevel;
	this->logType  = logType;

	this->textLog.close();
	this->htmlLog.close();
//	this->xmlLog.close();

	tm pTime;
	time_t lTime;
	time(&lTime);
	localtime_s(&pTime, &lTime);

	if((this->logType & LT_CONSOLE) && GetConsoleWindow()==NULL)
	{
		AllocConsole();

		// redirect the stdout to console
		FILE* stream;
		freopen_s(&stream, "CONOUT$", "w+t", stdout);
	}

	if(this->logType & LT_TEXT)
	{
		this->textLog.open((logFile + ".txt").c_str());
		this->textLog.setf(std::ios::unitbuf);
		this->textLog.clear();

		// write the head 
		this->textLog << "\t   * LogFile *" << endl << endl;

		this->textLog << "Created on ";
		this->textLog << std::setfill('0') << std::setw(2) << pTime.tm_mday << "/" << std::setfill('0') << std::setw(2) << pTime.tm_mon << "/" << (pTime.tm_year+1900);
		this->textLog << " at ";
		this->textLog << std::setfill('0') << std::setw(2) << pTime.tm_hour << ":" << std::setfill('0') << std::setw(2) << pTime.tm_min << ":" << std::setfill('0') << std::setw(2) << pTime.tm_sec;
		this->textLog << endl << "*********************************" << endl << endl;
	}

	if(this->logType & LT_HTML)
	{
		this->htmlLog.open((logFile + ".html").c_str());
		this->htmlLog.setf(std::ios::unitbuf);
		this->htmlLog.clear();

		// write the head
		this->htmlLog << "<html><head><title>Log File</title></head><body>" << endl;
		this->htmlLog << "<font style=\"font-family: \'Comic Sans MS\'\">" << endl;
		this->htmlLog << "<center><h1>LogFile</h1>" << endl;
		this->htmlLog << "<h3>Created on ";
		this->htmlLog << "<i>" << std::setfill('0') << std::setw(2) << pTime.tm_mday << "/" << std::setfill('0') << std::setw(2) << pTime.tm_mon << "/" << (pTime.tm_year+1900) << "</i>";
		this->htmlLog << " at ";
		this->htmlLog << "<i>" << std::setfill('0') << std::setw(2) << pTime.tm_hour << ":" << std::setfill('0') << std::setw(2) << pTime.tm_min << ":" << std::setfill('0') << std::setw(2) << pTime.tm_sec << "</i>";
		this->htmlLog << "</h3></center></font>" << endl;
		this->htmlLog << "<hr style=\"width: 75%; height: 3px;\" noshade/>" << endl;
	}

// 	if(this->logType & LT_XML)
// 	{
// 		this->xmlLog.open((logFile + ".xml").c_str());
// 		this->xmlLog.setf(std::ios::unitbuf);
// 		this->xmlLog.clear();
// 		// write the head
// 	}
}

void SimpleLogger::Close()
{
// 	if(this->logType & LT_CONSOLE)
// 	{
// 		FreeConsole();
// 	}

	if(this->textLog.is_open())
	{
		this->textLog.close();
	}

	if(this->htmlLog.is_open())
	{
		// write the tail
		this->htmlLog << "</body></html>" << endl;

		this->htmlLog.close();
	}

// 	if(this->xmlLog.is_open())
// 	{
// 		// write the tail
// 		this->xmlLog.close();
// 	}

	if(this->localLog.is_open())
	{
		this->localLog.close();
	}
}

SimpleLogger& SimpleLogger::operator << (LogLevel level)
{
	this->localLogging = true;
	this->localLogStr = "";
	this->localLogLevel = level;

	return *this;
}

SimpleLogger& SimpleLogger::operator << (int i)
{
	if(!this->localLogging)
		return *this;

	this->localLogStr += this->NumToStr(i);

	return *this;
}

SimpleLogger& SimpleLogger::operator << (unsigned int ui)
{
	if(!this->localLogging)
		return *this;

	this->localLogStr += this->NumToStr(ui);

	return *this;
}

SimpleLogger& SimpleLogger::operator << (long l)
{
	if(!this->localLogging)
		return *this;

	this->localLogStr += this->NumToStr(l);

	return *this;
}

SimpleLogger& SimpleLogger::operator << (unsigned long ul)
{
	if(!this->localLogging)
		return *this;

	this->localLogStr += this->NumToStr(ul);

	return *this;
}

SimpleLogger& SimpleLogger::operator << (float f)
{
	if(!this->localLogging)
		return *this;

	this->localLogStr += this->NumToStr(f);

	return *this;
}

SimpleLogger& SimpleLogger::operator << (double d)
{
	if(!this->localLogging)
		return *this;

	this->localLogStr += this->NumToStr(d);

	return *this;
}

SimpleLogger& SimpleLogger::operator << (const char *chs)
{
	if(!this->localLogging)
		return *this;

	if(strcmp(chs, "\n") == 0)
	{
		this->WriteLog(this->GetDateAndTime(), this->localLogLevel, this->localLogStr);
		this->localLogStr = "";
		this->localLogging = false;
	}
	else
	{
		this->localLogStr.append(chs);
	}

	return *this;
}

SimpleLogger& SimpleLogger::operator << (std::string str)
{
	if(!this->localLogging)
		return *this;

	this->localLogStr += str;

	return *this;
}

void SimpleLogger::Log(LogLevel level, const char* format, ...)
{
	if(level<this->logLevel || !format)
		return;

	va_list argList;
	va_start(argList, format);

	this->WriteLog(this->GetDateAndTime(), level, this->ParseParams(argList, format));
}

void SimpleLogger::LocalLog(std::string logFile, const char* format, ...)
{
	if(logFile.empty() || !format)
		return;

	if(this->localLogFile.compare(logFile) != 0)
	{
		this->localLogFile = logFile;
		this->localLog.close();
		this->localLog.open((this->localLogFile + ".txt").c_str());
		this->localLog.setf(std::ios::unitbuf);
		this->localLog.clear();
	}

	va_list argList;
	va_start(argList, format);

	std::string dateTimeStr = this->GetDateAndTime();
	std::string msg = this->ParseParams(argList, format);

	this->localLog << dateTimeStr << this->GetLevelStr(LL_DEBUG) << msg << endl;
	this->WriteLog(dateTimeStr, LL_DEBUG, msg);
}

SimpleLogger::SimpleLogger()
{
	this->localLogging = false;
	this->localLogStr = "";
	this->localLogLevel = LL_DEBUG;
	this->logType = 0;
	this->logLevel = LL_DEBUG;
	this->logDate = true;
	this->logTime = true;
	this->localLogFile = "";

	this->GenerateLineHeading();
}

SimpleLogger::SimpleLogger(const SimpleLogger&)
{

}

SimpleLogger& SimpleLogger::operator =(const SimpleLogger &)
{
	return *this;
}

SimpleLogger::~SimpleLogger()
{
	this->Close();
}

std::string SimpleLogger::GetLevelStr(LogLevel level, bool html/* = false */)
{
	switch(level)
	{
	case LL_DEBUG:
		return html ? "Debug&nbsp;&nbsp; " : "Debug   ";

	case LL_INFO:
		return html ? "Info&nbsp;&nbsp;&nbsp; " : "Info    ";

	case LL_WARNING:
		return "Warning ";

	case LL_ERROR:
		return html ? "Error&nbsp;&nbsp; " : "Error   ";

	default:
		return "Unknown ";
	}
}

std::string SimpleLogger::GetLevelClr(LogLevel level)
{
	switch(level)
	{
	case LL_DEBUG:
		return "green";

	case LL_INFO:
		return "black";

	case LL_WARNING:
		return "purple";

	case LL_ERROR:
		return "red";

	default:
		return "blue";
	}
}

void SimpleLogger::GenerateLineHeading()
{
	this->htmlLineHeading = "<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
	this->textLineHeading = "\n";
	
	size_t spaceToAdd = this->GetDateAndTime().length() + this->GetLevelStr(LL_ERROR).length();
	for(size_t i=0; i<spaceToAdd; ++i)
	{
		this->htmlLineHeading += "&nbsp;";
		this->textLineHeading += " ";
	}
}

template<typename T> 
inline std::string SimpleLogger::NumToStr( T n )
{
	std::stringstream strstream;
	strstream << n;

	return strstream.str();
}

std::string SimpleLogger::GetDateAndTime()
{
	std::string dateTimeStr = "";

	time_t lTime;
	tm pTime;

	time(&lTime);
	localtime_s(&pTime, &lTime);

	if(this->logDate)
	{
		char dateStr[80];
		sprintf_s(dateStr, "%04d-%02d-%02d ", pTime.tm_year+1900, pTime.tm_mon+1, pTime.tm_mday);
		dateTimeStr.append(dateStr);
	}

	if(this->logTime)
	{
		char timeStr[80];
		sprintf_s(timeStr, "%02d:%02d:%02d ", pTime.tm_hour, pTime.tm_min, pTime.tm_sec);
		dateTimeStr.append(timeStr);
	}

	return dateTimeStr;
}

std::string SimpleLogger::ParseParams( va_list& argList, const char* format)
{
	std::string msg = format;

	std::size_t startPos = 0;
	std::size_t endPos = msg.length() - 1;

	std::string argStr = "";
	int replaceCnt = 0;
 
	while(1)
	{
		std::size_t findPos = msg.find('%', startPos);
		if(findPos==std::string::npos || findPos==endPos)
			break;

		replaceCnt = 0;
		argStr = "";

		switch(msg[findPos+1])
		{
		case 's':
			argStr = va_arg(argList, const char*);
			replaceCnt = 2;
			break;

		case 'd':
			argStr = this->NumToStr(va_arg(argList, int));
			replaceCnt = 2;
			break;

		case 'u':
			argStr = this->NumToStr(va_arg(argList, unsigned int));
			replaceCnt = 2;
			break;

		case 'l':
			if(findPos+2 > endPos)
			{
				va_end(argList);

				return msg;
			}

			if(msg[findPos+2] == 'd')
			{
				argStr = this->NumToStr(va_arg(argList, long));
				replaceCnt = 3;
			}
			else if(msg[findPos+2] == 'u')
			{
				argStr = this->NumToStr(va_arg(argList, unsigned long));
				replaceCnt = 3;
			}
			break;

		case 'f':
			argStr = this->NumToStr(va_arg(argList, double));
			replaceCnt = 2;
			break;

		default:
			++ findPos;
			break;
		}

		msg.replace(findPos, replaceCnt, argStr);
		startPos = findPos + argStr.length();
		endPos += argStr.length() - replaceCnt;
	}

	va_end(argList);

	return msg;
}

void SimpleLogger::ConvertBreak(std::string& msg, bool html)
{
	size_t startPos = 0;
	size_t findPos = std::string::npos;

	std::string& heading = html ? this->htmlLineHeading : this->textLineHeading;

	while(1)
	{
		findPos = msg.find('\n', startPos);
		if(findPos == std::string::npos)
			break;

		msg.replace(findPos, 1, heading);
		startPos = findPos + heading.length();
	}
}

void SimpleLogger::WriteLog(const std::string& dateTimeStr, LogLevel level, const std::string& msg)
{
	std::string textContent = msg;
	this->ConvertBreak(textContent, false);

	if(this->logType & LT_CONSOLE)
	{
		std::cout << dateTimeStr << this->GetLevelStr(level) << textContent << endl;
	}

	if(this->logType & LT_DEBUGWND)
	{
		OutputDebugString((dateTimeStr + this->GetLevelStr(level) + textContent).c_str());
		OutputDebugString("\n");
	}

	if((this->logType&LT_TEXT) && this->textLog.is_open() && this->textLog.good())
	{
		this->textLog << dateTimeStr << this->GetLevelStr(level) << textContent << endl;
	}

	if((this->logType&LT_HTML) && this->htmlLog.is_open() && this->htmlLog.good())
	{
		std::string htmlContent = msg;
		this->ConvertBreak(htmlContent, true);

		// write the html code
		this->htmlLog << "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" << endl;
		this->htmlLog << "<font color=\"" << this->GetLevelClr(level) <<"\" style=\"font-family: \'Courier New\'\" size=2><b>";
		this->htmlLog << dateTimeStr << this->GetLevelStr(level, true) << htmlContent << "</b></font><br/>" << endl;
	}

// 	if((this->logType&LT_XML) && this->textLog.is_open() && this->xmlLog.good())
// 	{
// 		// write the xml code
// 	}
}